﻿using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Linq;
using CompactMapper.Orm.Contracts;
using CompactMapper.Orm.Extensions;
using CompactMapper.Orm.Queries;
using CompactMapper.Orm.UnitOfWork.Mappings;

namespace CompactMapper.Orm.UnitOfWork
{
    public sealed class SqLiteKeyedUnitOfWork<T> : IKeyedSqLiteUnitOfWork<T>
        where T : class, IKeyedPersistent
    {
        private readonly String _connectionString;
        private readonly List<IKey> _deletePendingKeys;
        private readonly IdentityMap<T> _identityMap;
        private readonly IMappings _mappings;
        private List<T> _persistancePendingEntities;

        internal SqLiteKeyedUnitOfWork(string connectionString, IMappings mappings)
        {
            if (mappings == null)
            {
                throw new ArgumentNullException("mappings");
            }
            _mappings = mappings;

            if (connectionString == null)
            {
                throw new ArgumentNullException("connectionString");
            }
            _connectionString = connectionString;

            _persistancePendingEntities = new List<T>();
            _deletePendingKeys = new List<IKey>();
            _identityMap = new IdentityMap<T>();
        }

        public void MakePersistent(T entity)
        {
            if (!_persistancePendingEntities.Contains(entity))
                _persistancePendingEntities.Add(entity);
        }

        public void MakePersistent(IEnumerable<T> entities)
        {
            _persistancePendingEntities = _persistancePendingEntities.Union(entities).ToList();
        }

        public IEnumerable<T> GetAll()
        {
            using (var connectionWrapper = ConnectionFactory.CreateConnection(_connectionString))
            {
                var persisted = _mappings.RunGetAll<T>(connectionWrapper.Connection);
                persisted.Apply(_identityMap.Add);
                return persisted;
            }
        }

        public T GetByKey(IKey idValue)
        {
            if (!(_identityMap.Contains(idValue)))
            {
                using (var connectionWrapper = ConnectionFactory.CreateConnection(_connectionString))
                {
                    var entity =
                        _mappings.RunQuery<T>(connectionWrapper.Connection, new Query<T>().PrimaryKey(idValue)).
                            SingleOrDefault();
                    if (entity == null)
                    {
                        throw new KeyNotFoundException();
                    }
                    _identityMap.Add(entity);
                }
            }
            return _identityMap[idValue];
        }

        public IEnumerable<T> GetByQuery(Query<T> query)
        {
            using (var connectionWrapper = ConnectionFactory.CreateConnection(_connectionString))
            {
                var results = _mappings.RunQuery<T>(connectionWrapper.Connection, query);
                results.Apply(r => _identityMap.Add(r));
                return results;
            }
        }

        public void DeleteAll()
        {
            _identityMap.Clear();
            _deletePendingKeys.Clear();
            using (var connectionWrapper = ConnectionFactory.CreateConnection(_connectionString))
            {
                _mappings.RunDeleteAll<T>(connectionWrapper.Connection);
            }
        }

        public SQLiteDataReader GetReader(SQLiteCommand query)
        {
            var connectionWrapper = ConnectionFactory.CreateConnection(_connectionString);
            query.Connection = connectionWrapper.Connection;
            return query.ExecuteReader();
        }

        public void ExecuteNonQuery(SQLiteCommand command)
        {
            using (var connectionWrapper = ConnectionFactory.CreateConnection(_connectionString))
            {
                command.Connection = connectionWrapper.Connection;
                command.ExecuteNonQuery();
            }
        }

        public ITransaction CreateTransaction()
        {
            return new CompactMapperTransaction(_connectionString);
        }

        public void Delete(T keyedPersistent)
        {
            Delete(keyedPersistent.Key);
        }

        public void Delete(IKey key)
        {
            _identityMap.Remove(key);
            _deletePendingKeys.Add(key);
        }

        public Boolean PersistAll()
        {
            if (_deletePendingKeys.Any() || _persistancePendingEntities.Any())
            {
                var connectionWrapper = ConnectionFactory.CreateConnection(_connectionString);
                var transaction = connectionWrapper.Connection.BeginTransaction();
                try
                {
                    RunDeleteMappings(connectionWrapper.Connection);
                    RunInsertMappings(connectionWrapper.Connection);
                    transaction.Commit();
                    _deletePendingKeys.Clear();
                    _persistancePendingEntities.Clear();
                    return true;
                }
                catch
                {
                    transaction.Rollback();
                    return false;
                }
                finally
                {
                    connectionWrapper.Dispose();
                }
            }
            return true;
        }

        public void Dispose()
        {
            PersistAll();
        }

        private void RunDeleteMappings(SQLiteConnection connection)
        {
            var toDeleteCollection = _deletePendingKeys.ToArray();
            foreach (var toDelete in toDeleteCollection)
            {
                _mappings.RunDelete<T>(connection, toDelete);
            }
        }

        private void RunInsertMappings(SQLiteConnection connection)
        {
            _mappings.RunInsert(connection, _persistancePendingEntities.Cast<IKeyedPersistent>());
        }
    }
}